
#ifndef __SQLITE3X_HPP__
#define __SQLITE3X_HPP__

#include "dllmain.h"

#include <boost/shared_ptr.hpp>
#include <boost/utility.hpp>
#include <stdexcept>
#include <string>
#include <map>

#ifndef SQLITE_API
#define SQLITE_API
#endif

namespace sqlite3x {

    class SQLITE_API sqlite3_reader;

    class SQLITE_API sqlite3_connection : boost::noncopyable {
    private:
        friend class sqlite3_command;
        friend class database_error;

        struct sqlite3 *db;

    public:
        sqlite3_connection();
        sqlite3_connection(const char *db);
        sqlite3_connection(const wchar_t *db);
        ~sqlite3_connection();

        void open(const char *db);
        void open(const wchar_t *db);
        void close();

        long long insert_id();
        void set_busytimeout(int ms);

        void vacuum(void);
        void set_foreign_keys(bool foreign_keys_on = true);

        void execute_nonquery(const char *sql);
        void execute_nonquery(const wchar_t *sql);
        void execute_nonquery(const std::string &sql);
        void execute_nonquery(const std::wstring &sql);

        int execute_int(const char *sql);
        int execute_int(const wchar_t *sql);
        int execute_int(const std::string &sql);
        int execute_int(const std::wstring &sql);

        long long execute_int64(const char *sql);
        long long execute_int64(const wchar_t *sql);
        long long execute_int64(const std::string &sql);
        long long execute_int64(const std::wstring &sql);

        double execute_double(const char *sql);
        double execute_double(const wchar_t *sql);
        double execute_double(const std::string &sql);
        double execute_double(const std::wstring &sql);

        std::string execute_string(const char *sql);
        std::string execute_string(const wchar_t *sql);
        std::string execute_string(const std::string &sql);
        std::string execute_string(const std::wstring &sql);

        std::wstring execute_string16(const char *sql);
        std::wstring execute_string16(const wchar_t *sql);
        std::wstring execute_string16(const std::string &sql);
        std::wstring execute_string16(const std::wstring &sql);

        std::string execute_blob(const char *sql);
        std::string execute_blob(const wchar_t *sql);
        std::string execute_blob(const std::string &sql);
        std::string execute_blob(const std::wstring &sql);
    };

    class SQLITE_API sqlite3_transaction : boost::noncopyable {
    private:
        sqlite3_connection &con;
        bool intrans;

    public:
        sqlite3_transaction(sqlite3_connection &con, bool start=true);
        ~sqlite3_transaction();

        void begin();
        void commit();
        void rollback();
    };

    class SQLITE_API sqlite3_command : boost::noncopyable {
    private:
        friend class sqlite3_reader;

        sqlite3_connection &con;
        struct sqlite3_stmt *stmt;
        unsigned int refs;
        int argc;
        int bind_index;

    public:
        sqlite3_command(sqlite3_connection &con, const char *sql);
        sqlite3_command(sqlite3_connection &con, const wchar_t *sql);
        sqlite3_command(sqlite3_connection &con, const std::string &sql);
        sqlite3_command(sqlite3_connection &con, const std::wstring &sql);
        ~sqlite3_command();

    private:
        void bind(int index);                                   // null
        void bind(int index, int data);                         // int32
        void bind(int index, long data);                        // int32
        void bind(int index, long long data);                   // int64
        void bind(int index, double data);                      // real
        void bind(int index, const char *data, int datalen);
        void bind(int index, const wchar_t *data, int datalen);
        void bind(int index, const void *data, int datalen);
        void bind(int index, const std::string &data);
        void bind(int index, const std::wstring &data);

    public:
        sqlite3_command & bind_i(void);                             // null
        sqlite3_command & bind_i(int data);                         // int32
        sqlite3_command & bind_i(long data);                        // int32
        sqlite3_command & bind_i(long long data);                   // int64
        sqlite3_command & bind_i(double data);                      // real
        sqlite3_command & bind_i(const char *data, int datalen);
        sqlite3_command & bind_i(const wchar_t *data, int datalen);
        sqlite3_command & bind_i(const std::string & data);
        sqlite3_command & bind_i(const std::wstring & data);

        sqlite3_command & bind_i(unsigned int data){
            return bind_i((long long)data);
        }
        sqlite3_command & bind_i(unsigned short data){
            return bind_i((long long)data);
        }
        sqlite3_command & bind_i(unsigned long data){
            return bind_i((long long)data);
        }
        sqlite3_command & bind_i(unsigned long long data){
            return bind_i((long long)data);
        }

        const char *sql_text(void);

    public:
        sqlite3_reader execute_reader();
        void           execute_nonquery();
        int            execute_int();
        long long      execute_int64();
        double         execute_double();
        std::string    execute_string();
        std::wstring   execute_string16();
        std::string    execute_blob();
    };

    class SQLITE_API sqlite3_reader {
    private:
        friend class sqlite3_command;

        sqlite3_command *cmd;

        typedef std::map<std::string,int> colname_index_t;
        boost::shared_ptr<colname_index_t> colnames;

        sqlite3_reader(sqlite3_command *cmd);

    public:
        sqlite3_reader();
        sqlite3_reader(const sqlite3_reader &copy);
        ~sqlite3_reader();

        sqlite3_reader& operator=(const sqlite3_reader &copy);

        bool read();
        void reset();
        void close();

        int          get_int     (int index);
        long long    get_int64   (int index);
        double       get_double  (int index);
        std::string  get_string  (int index);
        std::wstring get_string16(int index);
        std::string  get_blob    (int index);

        std::string  get_colname  (int index);
        std::wstring get_colname16(int index);

        int          get_colindex(std::string colname);
        //int          get_colindex(std::wstring colname);

        int          get_int     (std::string colname);
        long long    get_int64   (std::string colname);
        double       get_double  (std::string colname);
        std::string  get_string  (std::string colname);
        std::wstring get_string16(std::string colname);
        std::string  get_blob    (std::string colname);
    };

    class SQLITE_API database_error : public std::runtime_error {
    public:
        database_error(const char *msg);
        database_error(sqlite3_connection &con);
    };
}

#endif
